【精选】用STM32F103完成对SD卡数的读写

您所在的位置:网站首页 gd32f103 usb读写 【精选】用STM32F103完成对SD卡数的读写

【精选】用STM32F103完成对SD卡数的读写

2023-10-18 09:32| 来源: 网络整理| 查看: 265

目录 一、前言二、题目要求三、SD卡协议了解1、SDIO协议简介2、SD卡物理结构3、SD卡寄存器列表4、SD卡初始化(SPI模式)5、SD卡读写(SPI模式) 四、使用CubeMX创建工程五、程序的编写六、硬件准备七、结果展示八、总结参考资料

一、前言

由于咱们使用的是STM32F103C8T6的最小系统并没有SDIO口,所以想要外接存储设备对数据进行存储必须使用SPI对我们SD卡中的数据进行读写。

二、题目要求

掌握SD卡协议原理,用STM32F103完成对SD卡的数据读取(fat文件模式)。

三、SD卡协议了解 1、SDIO协议简介

SD卡(Secure Digital Memory Card)在我们的生活中已经非常普遍了,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是 SPI接口,另外一种就是 SDIO接口。SDIO 全称是 安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O卡 都有 SDIO接口。STM32F103系列控制器有一个 SDIO主机接口,它可以与 MMC卡、SD卡、SD I/O卡 以及 CE-ATA 设备进行数据传输。

2、SD卡物理结构

一般SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器 5个部分。 在这里插入图片描述 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输; 电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位; 卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器; 接口驱动器控制SD卡引脚的输入输出。 SD卡总共有8个寄存器,用于设定或表示SD卡信息。

这些寄存器只能通过对应的命令访问,SDIO定义64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后,根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。

3、SD卡寄存器列表 名称bit宽度描述CID128卡识别号(Card identification number):用来识别的卡的个体号码(唯一的RCA16相对地址(Relative card address):卡的本地系统地址,初始化时,动态地由卡建议,主机核准。DSR16驱动级寄存器(Driver Stage Register):配置卡的输出驱动CSD128卡的特定数据(Card Specific Data):卡的操作条件信息SCR64SD配置寄存器(CD Configuration Register):SD卡特殊特性信息OCR32操作条件寄存器(Operation conditiongs register)SSR512SD状态(SD Status):SD卡专有特征的信息CSR32卡状态(Card Status):卡状态信息

详情请参考:SD卡协议简介

4、SD卡初始化(SPI模式)

SPI操作模式下:在SD卡收到复位命令时,CS为有效电平(低电平),则SPI模式被启用,在发送CMD之前要先发送74个时钟,64个为内部供电上升时间,10个用于SD卡同步;之后才能开始CMD操作,在初始化时CLK时钟不能超过400KHz。

1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置); 2、上电延时(>74个CLK); 3、复位卡(CMD0),进入IDLE状态; 4、发送CMD8,检查是否支持2.0协议; 5、根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58和CMD1等); 6、取消片选,发多8个CLK,结束初始化 这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。

5、SD卡读写(SPI模式)

1、发送CMD17; 2、接收卡响应R1; 3、接收数据起始令牌0XFE; 4、接收数据; 5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。 6、禁止片选之后,发多8个CLK; 以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下: 1、发送CMD24; 2、接收卡响应R1; 3、发送写数据起始令牌0XFE; 4、发送数据; 5、发送2字节的伪CRC; 6、禁止片选之后,发多8个CLK; 以上就是一个典型的写SD卡过程。

四、使用CubeMX创建工程

整体管脚配置预览 在这里插入图片描述 先配置SYS 在这里插入图片描述 配置PA4如图所示 在这里插入图片描述

这里不改名也可以,改名只是为了方便我们记忆它

配置USART1 在这里插入图片描述 配置SPI1为全双工主模式 在这里插入图片描述 配置FATFS 在这里插入图片描述 配置时钟树 在这里插入图片描述 工程配置 在这里插入图片描述

注:这里一定要修改堆栈的大小,太小了会使我们无法读取SD卡的数据,直接导致我们的程序跑飞

现在生成代码即可。

五、程序的编写

首先点击下载我们需要的代码 提取码:1111 下载完以后需要将这两个.C和.H文件复制到自己的工程目录下 在这里插入图片描述 紧接着咱们打开自己的MDK在其中添加刚刚复制过来的两个文件 在这里插入图片描述 添加文件并修改接口里面的内容映射到SPI上,可以直接将以下代码复制粘贴过去

uint8_t res; res = SD_init();//SD_Initialize() if(res)//STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常 { SPI_setspeed(SPI_BAUDRATEPRESCALER_256); spi_readwrite(0xff);//提供额外的8个时钟 SPI_setspeed(SPI_BAUDRATEPRESCALER_2); } if(res)return STA_NOINIT; else return RES_OK; //初始化成功

在这里插入图片描述 main.c:

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * © Copyright (c) 2019 STMicroelectronics. * All rights reserved. * * This software component is licensed by ST under Ultimate Liberty license * SLA0044, the "License"; You may not use this file except in compliance with * the License. You may obtain a copy of the License at: * www.st.com/SLA0044 * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "fatfs.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "SDdriver.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ SPI_HandleTypeDef hspi1; UART_HandleTypeDef huart1; /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_SPI1_Init(void); static void MX_USART1_UART_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (unsigned char *)&ch, 1, 0xFFFF); return ch; } uint16_t uart_value[3]; uint8_t aRxBuffer1; //uart rx buff void WritetoSD(BYTE write_buff[],uint8_t bufSize); char SD_FileName[] = "hello.txt"; uint8_t WriteBuffer[] = "01 窝室嫩叠\r\n"; //uint8_t test_sd =0; //用于测试格式化 uint8_t write_cnt =0; //写SD卡次数 void WritetoSD(BYTE write_buff[],uint8_t bufSize) { FATFS fs; FIL file; uint8_t res=0; UINT Bw; res = SD_init(); //SD卡初始化 if(res == 1) { printf("SD卡初始化失败! \r\n"); } else { printf("SD卡初始化成功! \r\n"); } res=f_mount(&fs,"0:",1); //挂载 // if(test_sd == 0) //用于测试格式化 if(res == FR_NO_FILESYSTEM) //没有文件系统,格式化 { // test_sd =1; //用于测试格式化 printf("没有文件系统! \r\n"); res = f_mkfs("", 0, 0); //格式化sd卡 if(res == FR_OK) { printf("格式化成功! \r\n"); res = f_mount(NULL,"0:",1); //格式化后先取消挂载 res = f_mount(&fs,"0:",1); //重新挂载 if(res == FR_OK) { printf("SD卡已经成功挂载,可以进进行文件写入测试!\r\n"); } } else { printf("格式化失败! \r\n"); } } else if(res == FR_OK) { printf("挂载成功! \r\n"); } else { printf("挂载失败! \r\n"); } res = f_open(&file,SD_FileName,FA_OPEN_ALWAYS |FA_WRITE); if((res & FR_DENIED) == FR_DENIED) { printf("卡存储已满,写入失败!\r\n"); } f_lseek(&file, f_size(&file));//确保写词写入不会覆盖之前的数据 if(res == FR_OK) { printf("打开成功/创建文件成功! \r\n"); res = f_write(&file,write_buff,bufSize,&Bw); //写数据到SD卡 if(res == FR_OK) { printf("文件写入成功! \r\n"); } else { printf("文件写入失败! \r\n"); } } else { printf("打开文件失败!\r\n"); } f_close(&file); //关闭文件 f_mount(NULL,"0:",1); //取消挂载 } void Get_SDCard_Capacity(void) { FRESULT result; FATFS FS; FATFS *fs; DWORD fre_clust,AvailableSize,UsedSize; uint16_t TotalSpace; uint8_t res; res = SD_init(); //SD卡初始化 if(res == 1) { printf("SD卡初始化失败! \r\n"); } else { printf("SD卡初始化成功! \r\n"); } /* 挂载 */ res=f_mount(&FS,"0:",1); //挂载 if (res != FR_OK) { printf("FileSystem Mounted Failed (%d)\r\n", result); } res = f_getfree("0:", &fre_clust, &fs); /* 根目录 */ if ( res == FR_OK ) { TotalSpace=(uint16_t)(((fs->n_fatent - 2) * fs->csize ) / 2 /1024); AvailableSize=(uint16_t)((fre_clust * fs->csize) / 2 /1024); UsedSize=TotalSpace-AvailableSize; /* Print free space in unit of MB (assuming 512 bytes/sector) */ printf("\r\n%d MB total drive space.\r\n""%d MB available.\r\n""%d MB used.\r\n",TotalSpace, AvailableSize,UsedSize); } else { printf("Get SDCard Capacity Failed (%d)\r\n", result); } } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); MX_FATFS_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ HAL_UART_Receive_IT(&huart1,&aRxBuffer1,1); //enable uart printf(" main \r\n"); Get_SDCard_Capacity(); //得到使用内存并选择格式化 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { WritetoSD(WriteBuffer,sizeof(WriteBuffer)); // HAL_Delay(500); WriteBuffer[0] = WriteBuffer[0] +0; WriteBuffer[1] = WriteBuffer[1] +1; write_cnt ++; while(write_cnt > 5) { printf(" while \r\n"); HAL_Delay(500); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV2; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /** * @brief SPI1 Initialization Function * @param None * @retval None */ static void MX_SPI1_Init(void) { /* USER CODE BEGIN SPI1_Init 0 */ /* USER CODE END SPI1_Init 0 */ /* USER CODE BEGIN SPI1_Init 1 */ /* USER CODE END SPI1_Init 1 */ /* SPI1 parameter configuration*/ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SPI1_Init 2 */ /* USER CODE END SPI1_Init 2 */ } /** * @brief USART1 Initialization Function * @param None * @retval None */ static void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART1_Init 2 */ /* USER CODE END USART1_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_RESET); /*Configure GPIO pin : SD_CS_Pin */ GPIO_InitStruct.Pin = SD_CS_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(SD_CS_GPIO_Port, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ 六、硬件准备

咱们用于读取SD卡的模块为 在这里插入图片描述 咱们可以去其店铺下载对应资料 由于采用SPI方式,所以我们的连线方式为

SD卡模块STM32CSPA4SCKPA5MOSIPA6MISOPA7VCC5VGNDGND

注:这里特别说明一下!!!大家一定要注意,我们在用USB转TTL为STM32供电的时候一定需要接到5V上面去,并且在用32给SD卡模块供电的时候一定一定要接到5V电源上,否则我们无法驱动该SD模块

在我第一次做这个实验的时候,我就将供电接的3V,发现除了能按一下复位键发送一个“main”以外并没有什么其他作用了,更别说对于SD卡的读取了。 经过长时间的探索后面才发现原来是3.3V的电压无法驱动我们的SD卡模块,也就无法进入到我们的SD卡初始化步骤了,咱们只需要将供电改成5V就可以得到正确结果了! 正确接线方式: STM32上的5V引脚为SD卡模块供电 请添加图片描述 USB转TTL为整个系统提供5V电压 请添加图片描述

七、结果展示

在这里插入图片描述 可以看到咱们的程序一共写入了六次文件,写入完成之后将进入while循环一直循环输出while。 我们可以根据这段代码来分析一下: 在这里插入图片描述 可以看出当我们的数据写入6次之后:write_cnt > 5就会进入一个死循环,每过0.5秒输出一个while。 接下来咱们再来验证一下,将SD卡拔出用读卡器插到我们的PC上看我们写入的文件 请添加图片描述 可以看到我们写入的文件为6行“01 write buff to sd”,这与我们的代码对应 在这里插入图片描述 但是大家可以看到我们打开的文件显示出来的结果是乱码的,我们要得到正确的结果就必须在main.c文件中修改一下我们写入数据的数组 在这里插入图片描述

这里建议循环次数小于10次,因为要想改变我们序号的十位就必须加一个计数十次的标志位,多此一举

修改写入文件的数据 在这里插入图片描述 再次实验 在这里插入图片描述 得到正确结果 在这里插入图片描述 实验完毕!

八、总结

在实验的过程中我遇到了不少难题,比如一开始的供电不足导致无法驱动SD卡模块,但是在大家的帮助下我终于还是解决了这个问题,另外就是咱们在移植代码的时候一定要仔细,稍微错一点可能就很麻烦会报很多错。最后,这篇博客是基于16G内存的SD卡的,小于16G内存的SD卡都可以用该工程来读取,大一些的SD卡就不得而知了,欢迎大家与我交流。

参考资料

SD卡协议简介 STM32用cube配置FATFS模式下SPI读写SD卡 完整工程下载 头文件下载 提取码:1111 对应店铺



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3